﻿#region Copyright
// Copyright (c) 2011-2012 RaisingForce Team (Alikin Sergey)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using RaisingForce.Raise.Presentation.ViewModel;

namespace RaisingForce.Raise.Tests
{


    /// <summary>
    ///Это класс теста для ItemSelectorViewModelTest, в котором должны
    ///находиться все модульные тесты ItemSelectorViewModelTest
    ///</summary>
    [TestClass()]
    public class ItemSelectorViewModelTest
    {


        private TestContext testContextInstance;

        /// <summary>
        ///Получает или устанавливает контекст теста, в котором предоставляются
        ///сведения о текущем тестовом запуске и обеспечивается его функциональность.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Дополнительные атрибуты теста
        // 
        //При написании тестов можно использовать следующие дополнительные атрибуты:
        //
        //ClassInitialize используется для выполнения кода до запуска первого теста в классе
        //[ClassInitialize()]
        //public static void MyClassInitialize(TestContext testContext)
        //{
        //}
        //
        //ClassCleanup используется для выполнения кода после завершения работы всех тестов в классе
        //[ClassCleanup()]
        //public static void MyClassCleanup()
        //{
        //}
        //
        //TestInitialize используется для выполнения кода перед запуском каждого теста
        //[TestInitialize()]
        //public void MyTestInitialize()
        //{
        //}
        //
        //TestCleanup используется для выполнения кода после завершения каждого теста
        //[TestCleanup()]
        //public void MyTestCleanup()
        //{
        //}
        //
        #endregion

        #region PropertyChangedValidator Class

        private class PropertyChangedValidator
        {
            public List<string> ValidProperties { get; set; }

            public List<string> InvalidProperties { get; set; }

            private readonly List<string> raisedProperties =
                new List<string>();

            private bool SelectionChanged { get; set; }

            public void SelectionChangedHandler(object sender, EventArgs e)
            {
                this.SelectionChanged = true;
            }

            public void PrepareSelectionChanged()
            {
                this.SelectionChanged = false;
            }

            public void PrepareRaisedProperties()
            {
                this.raisedProperties.Clear();
            }

            public void AssertSelectionChanged(bool expected)
            {
                if (this.SelectionChanged != expected)
                {
                    Assert.Fail(expected ?
                        "Expected SelectedChanged event was not raised." :
                        "SelectedChanged event is NOT expected.");
                }
            }

            public void AssertRaisedProperties()
            {
                CollectionAssert.AllItemsAreUnique(this.raisedProperties);
                if (this.ValidProperties != null)
                {
                    Assert.AreEqual(this.ValidProperties.Count, this.raisedProperties.Count);
                }
            }

            public PropertyChangedValidator()
            {
                this.ResetProperties();
            }

            public void ResetProperties()
            {
                this.ValidProperties = Enumerable.Empty<string>().ToList();
                this.InvalidProperties = Enumerable.Empty<string>().ToList();
            }

            public void MoveToValid(string propertyName)
            {
                Contract.Requires(propertyName != null);
                Contract.Requires(!this.ValidProperties.Contains(propertyName));
                Contract.Requires(this.InvalidProperties.Contains(propertyName));
                Contract.Ensures(this.ValidProperties.Contains(propertyName));
                Contract.Ensures(!this.InvalidProperties.Contains(propertyName));
                this.InvalidProperties.Remove(propertyName);
                this.ValidProperties.Add(propertyName);
            }

            public void MoveToInvalid(string propertyName)
            {
                Contract.Requires(propertyName != null);
                Contract.Requires(this.ValidProperties.Contains(propertyName));
                Contract.Requires(!this.InvalidProperties.Contains(propertyName));
                Contract.Ensures(!this.ValidProperties.Contains(propertyName));
                Contract.Ensures(this.InvalidProperties.Contains(propertyName));
                this.ValidProperties.Remove(propertyName);
                this.InvalidProperties.Add(propertyName);
            }

            public void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
            {
                string propertyName = e.PropertyName;
                if (this.InvalidProperties != null)
                {
                    if (this.InvalidProperties.Contains(propertyName))
                    {
                        Assert.Fail("This property should NOT change: {0}.", propertyName);
                    }
                }
                if (this.ValidProperties != null)
                {
                    if (!this.ValidProperties.Contains(propertyName))
                    {
                        Assert.Fail("This property should CHANGE: {0}.", propertyName);
                    }
                }
                this.raisedProperties.Add(propertyName);
            }
        }

        #endregion

        #region ValidatorRoutines Class

        private static class ValidatorRoutines
        {
            public static void InitHandlers<T>(
                PropertyChangedValidator validator,
                ItemSelectorViewModel<T> selector)
                where T : ItemClass, new()
            {
                Contract.Requires(validator != null);
                Contract.Requires(selector != null);
                selector.PropertyChanged += validator.PropertyChangedHandler;
                selector.SelectionChanged += validator.SelectionChangedHandler;
            }

            public static void RemoveHandlers<T>(
                PropertyChangedValidator validator,
                ItemSelectorViewModel<T> selector)
                where T : ItemClass, new()
            {
                Contract.Requires(validator != null);
                Contract.Requires(selector != null);
                selector.PropertyChanged -= validator.PropertyChangedHandler;
                selector.SelectionChanged -= validator.SelectionChangedHandler;
            }

            public static void AssertValidation(
                PropertyChangedValidator validator,
                bool expectedSelectionChanged,
                Action action)
            {
                Contract.Requires(validator != null);
                Contract.Requires(action != null);
                validator.PrepareSelectionChanged();
                validator.PrepareRaisedProperties();
                action();
                validator.AssertSelectionChanged(expectedSelectionChanged);
                validator.AssertRaisedProperties();
            }

            public static List<string> GetProperties(IEnumerable<string> items)
            {
                Contract.Requires(items != null);
                Contract.Requires(Contract.ForAll(items, item => !string.IsNullOrWhiteSpace(item)));
                Contract.Ensures(Contract.Result<List<string>>() != null);
                Contract.Ensures(Contract.ForAll(Contract.Result<List<string>>(), item => !string.IsNullOrWhiteSpace(item)));
                return items.ToList();
            }

            public static List<string> GetProperties(params string[] items)
            {
                Contract.Requires(items != null);
                Contract.Requires(Contract.ForAll(items, item => !string.IsNullOrWhiteSpace(item)));
                Contract.Ensures(Contract.Result<List<string>>() != null);
                Contract.Ensures(Contract.ForAll(Contract.Result<List<string>>(), item => !string.IsNullOrWhiteSpace(item)));
                return GetProperties((IEnumerable<string>)items);
            }

            public static void AwaitException<T1, T2, T3>(bool expected, Action action)
                where T1 : Exception
                where T2 : Exception
                where T3 : Exception
            {
                T1 exception1 = default(T1);
                T2 exception2 = default(T2);
                T3 exception3 = default(T3);
                try
                {
                    action();
                }
                catch (T1 ex)
                {
                    exception1 = ex;
                }
                catch (T2 ex)
                {
                    exception2 = ex;
                }
                catch (T3 ex)
                {
                    exception3 = ex;
                }
                bool exception1Thrown = exception1 != default(T1);
                bool exception2Thrown = exception2 != default(T1);
                bool exception3Thrown = exception3 != default(T1);
                bool exceptionThrown = exception1Thrown || exception2Thrown || exception3Thrown;
                if (exceptionThrown != expected)
                {
                    Assert.Fail(
                        string.Format(
                            expected ?
                            "Ожидалось исключение, которое не было вызвано: {0}" :
                            "Произошло исключение, которого не должно было быть: {0}",
                            exception1Thrown ? typeof(T1).FullName :
                            exception2Thrown ? typeof(T2).FullName :
                            typeof(T3).FullName));
                }
            }

            public static void AwaitException<T1, T2>(bool expected, Action action)
                where T1 : Exception
                where T2 : Exception
            {
                AwaitException<T1, T2, T2>(expected, action);
            }

            public static void AwaitException<T>(bool expected, Action action)
                where T : Exception
            {
                AwaitException<T, T, T>(expected, action);
            }
        }

        #endregion

        #region Properties Constants

        private const string propItems = "Items";

        private const string propItemsCount = "ItemsCount";

        private const string propSelectedItem = "SelectedItem";

        private const string propSelectedIndex = "SelectedIndex";

        private const string propIsReadOnly = "IsReadOnly";

        private readonly static IEnumerable<string> AllPropertiesForItems =
            new string[] 
            { 
                propItems, 
                propItemsCount, 
                propSelectedItem, 
                propSelectedIndex
            }
            .ToList()
            .AsReadOnly();

        private readonly static IEnumerable<string> AllProperties =
            new string[] 
            { 
                propIsReadOnly
            }
            .Union(AllPropertiesForItems)
            .ToList()
            .AsReadOnly();

        #endregion

        #region ItemClass and ItemRoutines Classes

        public class ItemClass
        {
            public int Value { get; set; }
        }

        public static class ItemRoutines
        {
            public static T Create<T>(int value)
                where T : ItemClass, new()
            {
                return new T { Value = value };
            }

            public static ObservableCollection<T> CreateMany<T>(params int[] values)
                where T : ItemClass, new()
            {
                Contract.Requires(values != null);
                Contract.Ensures(Contract.Result<ObservableCollection<T>>() != null);
                return new ObservableCollection<T>(
                    values.Select(x => Create<T>(x)).ToList());
            }
        }

        #endregion

        /// <summary>
        ///Тест для Items
        ///</summary>
        public void ItemsTestHelper<T>()
            where T : ItemClass, new()
        {
            // PROLOG
            ItemSelectorViewModel<T> target = new ItemSelectorViewModel<T>();
            var validator = new PropertyChangedValidator();
            ValidatorRoutines.InitHandlers(validator, target);
            ObservableCollection<T> expected = null;
            ObservableCollection<T> actual;
            // null -> null, no propertyChanges
            validator.ValidProperties = ValidatorRoutines.GetProperties();
            validator.InvalidProperties = ValidatorRoutines.GetProperties(AllPropertiesForItems);
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                action: () => target.Items = expected);
            // Items property generates OnPropertyChanged(propItems)            
            validator.MoveToValid(propItems);
            actual = target.Items;
            Assert.IsNotNull(actual);
            Assert.AreEqual(0, actual.Count);
            // Items initialized, now it could change Items property
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                action: () => target.Items = expected);
            // Items was set to null, and now it sets to new() => change of Items property
            expected = new ObservableCollection<T>();
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                action: () => target.Items = expected);
            // Items normal change new() -> new()
            expected = new ObservableCollection<T>();
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                action: () => target.Items = expected);
            // Items not changes
            validator.ValidProperties = ValidatorRoutines.GetProperties();
            validator.InvalidProperties = ValidatorRoutines.GetProperties(AllPropertiesForItems);
            expected = target.Items;
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                action: () => target.Items = expected);
            actual = target.Items;
            Assert.AreSame(expected, actual);
            // Items change: new() -> item(1). It sets SelectedItem and SelectedIndex properties.
            validator.ValidProperties = ValidatorRoutines.GetProperties(AllPropertiesForItems);
            validator.InvalidProperties = ValidatorRoutines.GetProperties();
            expected = ItemRoutines.CreateMany<T>(1);
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                action: () => target.Items = expected);
            actual = target.Items;
            CollectionAssert.AreEquivalent(expected, actual);
            Assert.AreEqual(1, target.ItemsCount);
            Assert.AreSame(expected.First(), target.SelectedItem);
            Assert.AreEqual(0, target.SelectedIndex);
            Assert.IsTrue(target.ContainsItem(expected.First()));
            Assert.IsFalse(target.ContainsItem(ItemRoutines.Create<T>(1)));
            Assert.AreEqual(0, target.IndexOfItem(expected.First()));
            Assert.AreEqual(
                ItemSelectorViewModel<T>.UnknownIndex,
                target.IndexOfItem(ItemRoutines.Create<T>(1)));
            // Items not changes
            validator.ValidProperties = ValidatorRoutines.GetProperties();
            validator.InvalidProperties = ValidatorRoutines.GetProperties(AllPropertiesForItems);
            expected = target.Items;
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                action: () => target.Items = expected);
            actual = target.Items;
            Assert.AreSame(expected, actual);
            // Items change: item(1) -> items(2)
            validator.ValidProperties = ValidatorRoutines.GetProperties(propItems, propSelectedItem);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propItemsCount, propSelectedIndex);
            expected = ItemRoutines.CreateMany<T>(2);
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                action: () => target.Items = expected);
            actual = target.Items;
            CollectionAssert.AreEquivalent(expected, actual);
            Assert.AreEqual(1, target.ItemsCount);
            Assert.AreEqual(0, target.SelectedIndex);
            Assert.AreEqual(expected.First(), target.SelectedItem);
            // Items change: item(2) -> items(3, 4)
            validator.ValidProperties = ValidatorRoutines.GetProperties(propItems, propSelectedItem, propItemsCount);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propSelectedIndex);
            expected = ItemRoutines.CreateMany<T>(3, 4);
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                action: () => target.Items = expected);
            actual = target.Items;
            CollectionAssert.AreEquivalent(expected, actual);
            Assert.AreEqual(2, target.ItemsCount);
            Assert.AreEqual(0, target.SelectedIndex);
            Assert.AreEqual(expected.First(), target.SelectedItem);
            // Items change: items(3, 4) -> items(5, old_item(3))
            validator.ValidProperties = ValidatorRoutines.GetProperties(propItems, propSelectedItem);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propItemsCount, propSelectedIndex);
            expected = new ObservableCollection<T>(
                new T[] { ItemRoutines.Create<T>(5), target.SelectedItem });
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
               action: () => target.Items = expected);
            actual = target.Items;
            CollectionAssert.AreEquivalent(expected, actual);
            Assert.AreEqual(2, target.ItemsCount);
            Assert.AreEqual(0, target.SelectedIndex);
            Assert.AreEqual(expected.First(), target.SelectedItem);
            // EPILOG
            ValidatorRoutines.RemoveHandlers(validator, target);
        }

        [TestMethod()]
        public void ItemsTest()
        {
            ItemsTestHelper<ItemClass>();
        }

        /// <summary>
        ///Тест для SelectedIndex
        ///</summary>
        public void SelectedIndexTestHelper<T>()
            where T : ItemClass, new()
        {
            // PROLOG
            const int UnknownIndex = ItemSelectorViewModel<T>.UnknownIndex;
            ItemSelectorViewModel<T> target = new ItemSelectorViewModel<T>();
            var validator = new PropertyChangedValidator();
            ValidatorRoutines.InitHandlers(validator, target);
            // Items is null
            validator.ValidProperties = ValidatorRoutines.GetProperties();
            validator.InvalidProperties = ValidatorRoutines.GetProperties(AllPropertiesForItems);
            int expected = UnknownIndex;
            int actual = 0;
            ValidatorRoutines.AwaitException<ArgumentException, ArgumentOutOfRangeException>(false,
                () => actual = target.SelectedIndex);
            Assert.AreEqual(expected, actual);
            // Items is null, trying to set SelectedIndex to some value
            expected = UnknownIndex;
            ValidatorRoutines.AssertValidation(validator, false,
                () => ValidatorRoutines.AwaitException<ArgumentException>(true,
                    () => target.SelectedIndex = 0));
            actual = target.SelectedIndex;
            Assert.AreEqual(expected, actual);
            Assert.IsNull(target.SelectedItem);
            // Items = item(1), SelectedIndex == 0, SelectedItem == 1
            validator.ValidProperties = ValidatorRoutines.GetProperties(AllPropertiesForItems);
            validator.InvalidProperties = ValidatorRoutines.GetProperties();
            expected = 0;
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                action: () => target.Items = ItemRoutines.CreateMany<T>(1));
            actual = target.SelectedIndex;
            Assert.AreEqual(expected, actual);
            Assert.IsNotNull(target.SelectedItem);
            Assert.IsTrue(target.SelectedItem.Value == 1);
            // Items = item(1), SelectedIndex = UnknownIndex, SelectedItem not changed
            validator.ValidProperties = ValidatorRoutines.GetProperties();
            validator.InvalidProperties = ValidatorRoutines.GetProperties(AllPropertiesForItems);
            expected = 0;
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                action: () => ValidatorRoutines.AwaitException<ArgumentOutOfRangeException>(true,
                    () => target.SelectedIndex = UnknownIndex));
            actual = target.SelectedIndex;
            Assert.AreEqual(expected, actual);
            Assert.IsNotNull(target.SelectedItem);
            Assert.IsTrue(target.SelectedItem.Value == 1);
            // Items = item(1), SelectedIndex = 1, SelectedItem not changed
            expected = 0;
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                action: () => ValidatorRoutines.AwaitException<ArgumentOutOfRangeException>(true,
                    () => target.SelectedIndex = 1));
            actual = target.SelectedIndex;
            Assert.AreEqual(expected, actual);
            Assert.IsNotNull(target.SelectedItem);
            Assert.IsTrue(target.SelectedItem.Value == 1);
            // Items = items(2, 3), SelectedIndex == 0, SelectedItem == 2
            validator.ValidProperties = ValidatorRoutines.GetProperties(propItems, propItemsCount, propSelectedItem);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propSelectedIndex);
            expected = 0;
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                action: () => target.Items = ItemRoutines.CreateMany<T>(2, 3));
            actual = target.SelectedIndex;
            Assert.AreEqual(expected, actual);
            Assert.IsNotNull(target.SelectedItem);
            Assert.IsTrue(target.SelectedItem.Value == 2);
            // Items = items(2, 3), SelectedIndex = 1, SelectedItem == 3
            validator.ValidProperties = ValidatorRoutines.GetProperties(propSelectedItem, propSelectedIndex);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propItems, propItemsCount);
            expected = 1;
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                action: () => ValidatorRoutines.AwaitException<ArgumentException, ArgumentOutOfRangeException>(false,
                    () => target.SelectedIndex = 1));
            actual = target.SelectedIndex;
            Assert.AreEqual(expected, actual);
            Assert.IsNotNull(target.SelectedItem);
            Assert.IsTrue(target.SelectedItem.Value == 3);
            // Items = items(2, 3), SelectedIndex = 1 (again), SelectedItem not changed
            validator.ValidProperties = ValidatorRoutines.GetProperties();
            validator.InvalidProperties = ValidatorRoutines.GetProperties(AllPropertiesForItems);
            expected = 1;
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                action: () => ValidatorRoutines.AwaitException<ArgumentException, ArgumentOutOfRangeException>(false,
                    () => target.SelectedIndex = 1));
            actual = target.SelectedIndex;
            Assert.AreEqual(expected, actual);
            Assert.IsNotNull(target.SelectedItem);
            Assert.IsTrue(target.SelectedItem.Value == 3);
            // Items = items(2, 3), SelectedIndex = 0, SelectedItem == 2
            validator.ValidProperties = ValidatorRoutines.GetProperties(propSelectedItem, propSelectedIndex);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propItems, propItemsCount);
            expected = 0;
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                action: () => ValidatorRoutines.AwaitException<ArgumentException, ArgumentOutOfRangeException>(false,
                    () => target.SelectedIndex = 0));
            actual = target.SelectedIndex;
            Assert.AreEqual(expected, actual);
            Assert.IsNotNull(target.SelectedItem);
            Assert.IsTrue(target.SelectedItem.Value == 2);
            // changing Items collection: remove item(3)
            // Items == item(2), SelectedIndex == 0, SelectedItem == 2
            validator.ValidProperties = ValidatorRoutines.GetProperties(propItemsCount);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propItems, propSelectedIndex, propSelectedItem);
            expected = 0;
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                action: () => target.Items.Remove(target.Items.First(x => x.Value == 3)));
            actual = target.SelectedIndex;
            Assert.AreEqual(expected, actual);
            Assert.AreEqual(1, target.ItemsCount);
            Assert.IsNotNull(target.SelectedItem);
            Assert.IsTrue(target.SelectedItem.Value == 2);
            // changing Items collection: insert item(4) into position 0
            // Items == items(4, 2), SelectedIndex == 1, SelectedItem == 2
            validator.ValidProperties = ValidatorRoutines.GetProperties(propItemsCount, propSelectedIndex);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propItems, propSelectedItem);
            expected = 1;
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                action: () => target.Items.Insert(0, ItemRoutines.Create<T>(4)));
            actual = target.SelectedIndex;
            Assert.AreEqual(expected, actual);
            Assert.AreEqual(2, target.ItemsCount);
            Assert.IsNotNull(target.SelectedItem);
            Assert.IsTrue(target.SelectedItem.Value == 2);
            // changing Items collection: remove selected item(2)
            // Items == item(4), SelectedIndex == 0, SelectedItem == 4
            validator.ValidProperties = ValidatorRoutines.GetProperties(propItemsCount, propSelectedIndex, propSelectedItem);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propItems);
            expected = 0;
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                action: () => target.Items.RemoveAt(target.SelectedIndex));
            actual = target.SelectedIndex;
            Assert.AreEqual(expected, actual);
            Assert.AreEqual(1, target.ItemsCount);
            Assert.IsNotNull(target.SelectedItem);
            Assert.IsTrue(target.SelectedItem.Value == 4);
            // changing Items collection: remove all items
            // Items == empty, SelectedIndex == UnknownIndex, SelectedItem == null
            validator.ValidProperties = ValidatorRoutines.GetProperties(propItemsCount, propSelectedIndex, propSelectedItem);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propItems);
            expected = UnknownIndex;
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                action: () => target.Items.Clear());
            actual = target.SelectedIndex;
            Assert.AreEqual(expected, actual);
            Assert.AreEqual(0, target.ItemsCount);
            Assert.IsNull(target.SelectedItem);
            // adding item again
            // Items == item(5), SelectedIndex == 0, SelectedItem == 5
            expected = 0;
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                action: () => target.Items.Add(ItemRoutines.Create<T>(5)));
            actual = target.SelectedIndex;
            Assert.AreEqual(expected, actual);
            Assert.AreEqual(1, target.ItemsCount);
            Assert.IsNotNull(target.SelectedItem);
            Assert.IsTrue(target.SelectedItem.Value == 5);
            // add item item(6), set as selected, and then set another Items = item(7)
            validator.ValidProperties = ValidatorRoutines.GetProperties(AllPropertiesForItems);
            validator.InvalidProperties = ValidatorRoutines.GetProperties();
            target.Items.Add(ItemRoutines.Create<T>(6));
            expected = 1;
            target.SelectedIndex = target.IndexOfItem(target.Items.First(x => x.Value == 6));
            actual = target.SelectedIndex;
            Assert.AreEqual(expected, actual);
            expected = 0;
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                action: () => target.Items = ItemRoutines.CreateMany<T>(7));
            actual = target.SelectedIndex;
            Assert.AreEqual(expected, actual);
            // EPILOG
            ValidatorRoutines.RemoveHandlers(validator, target);
        }

        [TestMethod()]
        public void SelectedIndexTest()
        {
            SelectedIndexTestHelper<ItemClass>();
        }

        /// <summary>
        ///Тест для SelectedItem
        ///</summary>
        public void SelectedItemTestHelper<T>()
            where T : ItemClass, new()
        {
            // PROLOG
            //const int UnknownIndex = ItemSelectorViewModel<T>.UnknownIndex;
            ItemSelectorViewModel<T> target = new ItemSelectorViewModel<T>();
            var validator = new PropertyChangedValidator();
            ValidatorRoutines.InitHandlers(validator, target);
            // Items == null, SelectedItem == null
            Assert.IsNull(target.SelectedItem);
            // Items == null, SelectedItem = null
            validator.ValidProperties = ValidatorRoutines.GetProperties();
            validator.InvalidProperties = ValidatorRoutines.GetProperties(AllPropertiesForItems);
            T expected = null;
            T actual;
            ValidatorRoutines.AwaitException<ArgumentNullException>(false,
                () => ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                    action: () => target.SelectedItem = expected));
            actual = target.SelectedItem;
            Assert.AreSame(expected, actual);
            // Items == null, SelectedItem = something
            expected = null;
            ValidatorRoutines.AwaitException<ArgumentException>(true,
                () => ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                    action: () => target.SelectedItem = ItemRoutines.Create<T>(0)));
            actual = target.SelectedItem;
            Assert.AreSame(expected, actual);
            // Items = item(1), SelectedItem == item(1)
            validator.ValidProperties = ValidatorRoutines.GetProperties(AllPropertiesForItems);
            validator.InvalidProperties = ValidatorRoutines.GetProperties();
            expected = ItemRoutines.Create<T>(1);
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                action: () => target.Items.Add(expected));
            actual = target.SelectedItem;
            Assert.AreSame(expected, actual);
            Assert.AreEqual(target.IndexOfItem(expected), target.SelectedIndex);
            // Items == item(1), SelectedItem = item(1) <- nothing changes here
            validator.ValidProperties = ValidatorRoutines.GetProperties();
            validator.InvalidProperties = ValidatorRoutines.GetProperties(AllPropertiesForItems);
            ValidatorRoutines.AwaitException<ArgumentNullException, ArgumentException>(false,
               () => ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                   action: () => target.SelectedItem = expected));
            actual = target.SelectedItem;
            Assert.AreSame(expected, actual);
            Assert.AreEqual(target.IndexOfItem(expected), target.SelectedIndex);
            // Items == item(1), SelectedItem = null
            ValidatorRoutines.AwaitException<ArgumentNullException>(true,
               () => ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                   action: () => target.SelectedItem = null));
            actual = target.SelectedItem;
            Assert.AreSame(expected, actual);
            // Items == item(1), SelectedItem = something
            ValidatorRoutines.AwaitException<ArgumentException>(true,
               () => ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                   action: () => target.SelectedItem = ItemRoutines.Create<T>(0)));
            actual = target.SelectedItem;
            Assert.AreSame(expected, actual);
            // insert item(2) before item(1)
            // Items = items(2, 1), SelectedItem == item(1)
            validator.ValidProperties = ValidatorRoutines.GetProperties(propItemsCount, propSelectedIndex);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propItems, propSelectedItem);
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                action: () => target.Items.Insert(0, ItemRoutines.Create<T>(2)));
            actual = target.SelectedItem;
            Assert.AreSame(expected, actual);
            Assert.AreEqual(1, target.IndexOfItem(actual));
            // select item(2)
            // Items == items(2, 1), SelectedItem = item(2)
            validator.ValidProperties = ValidatorRoutines.GetProperties(propSelectedItem, propSelectedIndex);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propItems, propItemsCount);
            expected = target.Items.First(x => x.Value == 2);
            ValidatorRoutines.AwaitException<ArgumentNullException, ArgumentException>(false,
               () => ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                   action: () => target.SelectedItem = expected));
            actual = target.SelectedItem;
            Assert.AreSame(expected, actual);
            Assert.AreEqual(0, target.IndexOfItem(actual));
            // select item(1) again
            // Items == items(2, 1), SelectedItem = item(1)
            expected = target.Items.First(x => x.Value == 1);
            ValidatorRoutines.AwaitException<ArgumentNullException, ArgumentException>(false,
               () => ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                   action: () => target.SelectedItem = expected));
            actual = target.SelectedItem;
            Assert.AreSame(expected, actual);
            Assert.AreEqual(1, target.IndexOfItem(actual));
            // remove item(2)
            // Items = item(1), SelectedItem == item(1)
            validator.ValidProperties = ValidatorRoutines.GetProperties(propItemsCount, propSelectedIndex);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propItems, propSelectedItem);
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                action: () => target.Items.Remove(target.Items.First(x => x.Value == 2)));
            actual = target.SelectedItem;
            Assert.AreSame(expected, actual);
            Assert.AreEqual(0, target.IndexOfItem(actual));
            // clear all items
            // Items = empty, SelectedItem == null
            validator.ValidProperties = ValidatorRoutines.GetProperties(propItemsCount, propSelectedItem, propSelectedIndex);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propItems);
            T savedItem = target.Items.First();
            expected = null;
            ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                action: () => target.Items.Clear());
            actual = target.SelectedItem;
            Assert.AreSame(expected, actual);
            Assert.IsTrue(target.IndexOfItem(savedItem) < 0);
            // EPILOG
            ValidatorRoutines.RemoveHandlers(validator, target);
        }

        [TestMethod()]
        public void SelectedItemTest()
        {
            SelectedItemTestHelper<ItemClass>();
        }

        /// <summary>
        ///Тест для IsReadOnly
        ///</summary>
        public void IsReadOnlyTestHelper<T>()
            where T : ItemClass, new()
        {
            // PROLOG
            ItemSelectorViewModel<T> target = new ItemSelectorViewModel<T>();
            target.Items = ItemRoutines.CreateMany<T>(1, 2, 3);
            var validator = new PropertyChangedValidator();
            ValidatorRoutines.InitHandlers(validator, target);
            // set IsReadOnly = true
            validator.ValidProperties = ValidatorRoutines.GetProperties(propIsReadOnly);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propItems, propItemsCount, propSelectedIndex, propSelectedItem);
            bool expected = true;
            bool actual;
            ValidatorRoutines.AwaitException<InvalidOperationException>(false,
                () => ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                    action: () => target.IsReadOnly = expected));
            actual = target.IsReadOnly;
            Assert.AreEqual(expected, actual);
            // try to change any property -> catch InvalidOperationException
            validator.ValidProperties = ValidatorRoutines.GetProperties();
            validator.InvalidProperties = ValidatorRoutines.GetProperties(AllProperties);
            ValidatorRoutines.AwaitException<InvalidOperationException>(true,
                () => ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                    action: () => target.Items = ItemRoutines.CreateMany<T>(5, 6, 7)));
            ValidatorRoutines.AwaitException<InvalidOperationException>(true,
                () => ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                    action: () => target.SelectedIndex = 0));
            ValidatorRoutines.AwaitException<InvalidOperationException>(true,
                () => ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                    action: () => target.SelectedItem = target.Items.First()));
            // set IsReadOnly = false
            validator.ValidProperties = ValidatorRoutines.GetProperties(propIsReadOnly);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propItems, propItemsCount, propSelectedIndex, propSelectedItem);
            expected = false;
            ValidatorRoutines.AwaitException<InvalidOperationException>(false,
               () => ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: false,
                   action: () => target.IsReadOnly = expected));
            actual = target.IsReadOnly;
            Assert.AreEqual(expected, actual);
            // now property setting is correct
            // ... change Items property
            validator.ValidProperties = ValidatorRoutines.GetProperties(propItems, propSelectedItem);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propItemsCount, propSelectedIndex, propIsReadOnly);
            ValidatorRoutines.AwaitException<InvalidOperationException>(false,
                () => ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                    action: () => target.Items = ItemRoutines.CreateMany<T>(5, 6, 7)));
            Assert.IsTrue(target.Items.First().Value == 5);
            // ... change SelectedItem property
            validator.ValidProperties = ValidatorRoutines.GetProperties(propSelectedItem, propSelectedIndex);
            validator.InvalidProperties = ValidatorRoutines.GetProperties(propItems, propItemsCount, propIsReadOnly);
            ValidatorRoutines.AwaitException<InvalidOperationException>(false,
                () => ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                    action: () => target.SelectedItem = target.Items.First(x => x.Value == 6)));
            Assert.AreEqual(6, target.SelectedItem.Value);
            // ... change SelectedIndex property
            int selectedValue = 7;
            int selectedIndex = target.IndexOfItem(target.Items.First(x => x.Value == selectedValue));
            ValidatorRoutines.AwaitException<InvalidOperationException>(false,
                () => ValidatorRoutines.AssertValidation(validator, expectedSelectionChanged: true,
                    action: () => target.SelectedIndex = selectedIndex));
            Assert.AreEqual(selectedValue, target.SelectedItem.Value);
            Assert.AreEqual(selectedIndex, target.SelectedIndex);
            // EPILOG
            ValidatorRoutines.RemoveHandlers(validator, target);
        }

        [TestMethod()]
        public void IsReadOnlyTest()
        {
            IsReadOnlyTestHelper<ItemClass>();
        }
    }
}
